#include "BondSearch.h"

#include <bondlib/BondContainer.h>
#include <bondlib/Pinyin.h>
#include <bondlib/QBIssuerInfo.h>
#include <core/strings/string_util.h>
#include <include/CmdDefines.h>
#include <uam/ServerTimeMgr.h>

#include <QJsonObject>
#include <thread>

#include "Detail/BondDetailDlg.h"
#include "QbBondModule.h"

using namespace qb::base;

class compare_shortname {
 public:
  compare_shortname() {}
  bool operator()(int index1, int index2) {
    if (index1 == index2) return false;
    CQBIssuerInfo& IInfo = CQBIssuerInfo::instance();
    const IssuerInfo* pIssuer1 = IInfo.GetIssuerInfoByIndex(index1);
    const IssuerInfo* pIssuer2 = IInfo.GetIssuerInfoByIndex(index2);
    if (!pIssuer1 || !pIssuer2) return false;
    const char* name1 = pIssuer1->field((int)kIssuerInfo::Full_Name_C);
    const char* name2 = pIssuer2->field((int)kIssuerInfo::Full_Name_C);
    return strcmp(name1, name2) < 0;
  }
} sort_shortname;

void MainSearchEngine::search(int serialNum, const QString& key) {
  m_curSearchSerialNum = serialNum;
  QString searchKey = key.toUpper();
  std::thread t([=]() {
    {
      std::lock_guard<std::mutex> locker(m_lock);
      m_searchHis[searchKey] = serialNum;
    }

    doSearch(serialNum, searchKey);
  });
  t.detach();
}

void MainSearchEngine::setUserData(int userdata) {}

void MainSearchEngine::setResultCallback(
    std::function<void(int, QJsonArray)> fun) {
  m_fun = fun;
}

int MainSearchEngine::GetResultCount() const { return m_resCount; }

const std::vector<int>& MainSearchEngine::SortResults(const int* ids,
                                                      int& ncount) {
  // if (ncount > MAX_SORT_COUNT || ncount<=1)
  //	return ids;
  struct CodeInfo {
    std::string code;
    int bid;
    // int issue_date;
   public:
    bool operator<(const CodeInfo& ci) const {
      return code < ci.code; /*issue_date > ci.issue_date;*/
    }
  };

  std::set<int> indexes;
  std::vector<CodeInfo> newBond;
  newBond.reserve(ncount);
  std::vector<CodeInfo> addIssueBond;
  addIssueBond.reserve(ncount);
  std::vector<CodeInfo> expireBond;
  expireBond.reserve(ncount);

  int bond_count = CBondContainer::instance().GetSize();
  for (int i = 0; i < ncount; i++) {
    int bid = ids[i];
    if (bid < 0 || bid >= bond_count) continue;
    if (indexes.find(bid) != indexes.end()) {
      continue;
    }
    indexes.insert(bid);
    const CBondInfo& bi = CBondContainer::instance().ElementAtR(bid);
    CodeInfo ci;
    ci.bid = ids[i];
    ci.code = bi.GetBondCode();

    if (bi.IsExpireBond()) {  // 过期券
      expireBond.push_back(ci);
    } else if (bi.IsAddIssueBond()) {  // 续发券
      addIssueBond.push_back(ci);
    } else {  // 新发券
      newBond.push_back(ci);
    }
  }

  std::sort(newBond.begin(), newBond.end());
  std::sort(addIssueBond.begin(), addIssueBond.end());
  std::sort(expireBond.begin(), expireBond.end());

  std::vector<CodeInfo> codes;
  codes.reserve(ncount);
  codes.insert(codes.end(), newBond.begin(), newBond.end());
  codes.insert(codes.end(), addIssueBond.begin(), addIssueBond.end());
  codes.insert(codes.end(), expireBond.begin(), expireBond.end());

  m_vecSortedIds.clear();
  for (auto code : codes) {
    m_vecSortedIds.push_back(code.bid);
  }
  ncount = codes.size();
  return m_vecSortedIds;
}

static bool IsStartWith(const char* str, const char* text, int len) {
  // 不使用memcpy是避免使用strlen
  const char* tend = text + len;
  while (*str != 0 && text < tend) {
    if (*str++ != *text++) return false;
  }
  return len > 0 && text >= tend;
}

static const char* GbkStrStr(const char* source, const char* target) {
  if (source == NULL || *source == '\0') return NULL;
  if (target == NULL || *target == '\0') return NULL;
  const char* pos = strstr(source, target);

  // 当target为1个汉字的时候,有不小的概率出现
  // 为超过1个汉字,或者前后有英文字符,出现概率很低很低
  // 找到后,前后扫描,出现>=127字符的个数为奇数个,则认定是汉字gbk编码中间匹配,搜索失败
  // 测试范例: GbkStrStr("16悦来MTN001","美")
  if (pos != NULL && (unsigned char)(*target) >= 127) {
    int nlen = strlen(target);
    if (nlen == 2)  // 只有一个汉字的时候
    {
      int nprev = 0, nnext = 0;
      const char* ptr = pos - 1;
      while (ptr >= source && (unsigned char)(*ptr) >= 127)  // 往前扫描
      {
        nprev++;
        ptr--;
      }
      if (nprev % 2 == 1) return NULL;
      ptr = pos + nlen;
      while (*ptr != '\0' && (unsigned char)(*ptr) >= 127)  // 往后扫描
      {
        nnext++;
        ptr++;
      }
      if (nprev % 2 == 1) return NULL;
    }
  }
  return pos;
}

static void string_ToUpper(char* str, int len) {
  char* pos = str;
  for (int i = 0; i < len; i++, pos++) {
    if (*pos >= 'a' && *pos <= 'z') *pos = (*pos) - ('a' - 'A');
    if (*pos == 0) break;
  }
}

void MainSearchEngine::matchIssuers(const char* pText, int number,
                                    QJsonArray& results) {
  bool isNum = qb::base::StringUtil::IsNumber(pText, strlen(pText));
  if (isNum) return;

  CQBIssuerInfo& IInfo = CQBIssuerInfo::instance();
  const auto& imap = IInfo.GetAllIssuerInfo();
  int count = 0;
  int maxNum = 200;
  int exactmatchIndex = -1;
  int lenMatchTxt = strlen(pText);
  bool isCh = false;
  for (int i = 0; i < lenMatchTxt; i++) {
    if (pText[i] < 0 || pText[i] > 127) {
      isCh = true;
      break;
    }
  }
  std::vector<int> matchIndex;
  int issuerCount = IInfo.GetAllIssuerCount();
  for (int index = 0; index < issuerCount; index++) {
    if (count >= maxNum) break;
    const IssuerInfo* pIssuer = IInfo.GetIssuerInfoByIndex(index);
    if (!pIssuer) continue;
    bool found = false;

    do {
      // 全称匹配
      const char* matchStr = pIssuer->field((int)kIssuerInfo::Full_Name_C);
      found = (NULL != StringUtil::_strstr_cn(matchStr, (char*)pText));
      if (found) {
        if (strlen(matchStr) == lenMatchTxt) exactmatchIndex = index;
        break;
      }
      if (isCh) break;
      // 英文全称匹配
      matchStr = pIssuer->field((int)kIssuerInfo::Full_Name_E);
      found = (NULL != StringUtil::_strstr_cn(matchStr, (char*)pText));
      if (found) {
        if (strlen(matchStr) == lenMatchTxt) exactmatchIndex = index;
        break;
      }

      qb::PinyinManager& pyMgr = qb::PinyinManager::instance();
      std::string sJianpin, sQuanpin;
      pyMgr.GetPinyin(pIssuer->field((int)kIssuerInfo::Full_Name_C), sJianpin,
                      sQuanpin);
      // 简拼匹配
      found = (NULL != StringUtil::_strstr_cn(sJianpin.c_str(), (char*)pText));
      if (found) {
        if (sJianpin.size() == lenMatchTxt) exactmatchIndex = index;
        break;
      }

      found = (NULL != StringUtil::_strstr_cn(sQuanpin.c_str(),
                                              (char*)pText));  // 全拼匹配
      if (found) {
        if (sQuanpin.size() == lenMatchTxt) exactmatchIndex = index;
        break;
      }
    } while (0);

    if (found) {
      if (exactmatchIndex != index) matchIndex.push_back(index);
      ++count;
    }
  }

  if (count > 0) {
    QJsonArray result;
    QString str("title|issuer");
    result.push_back(str);
    result.push_back("发行人");
    results.push_back(result);

    std::sort(matchIndex.begin(), matchIndex.end(), sort_shortname);
    int addedCount = 0;
    if (exactmatchIndex != -1) {
      const IssuerInfo* pIssuer = IInfo.GetIssuerInfoByIndex(exactmatchIndex);
      if (!pIssuer) {
        return;
      }

      QJsonArray result;
      QString str("data|issuer|");
      str.append(QString::number(exactmatchIndex));
      result.push_back(str);
      str =
          QString::fromLocal8Bit(pIssuer->field((int)kIssuerInfo::Full_Name_C));
      if (!str.isEmpty()) {
        result.push_back(str);
        results.push_back(result);
      }

      ++addedCount;
    }
    for (auto& mIndex : matchIndex) {
      if (number > 0 && (int)results.size() >= number + 1) break;
      const IssuerInfo* pIssuer = IInfo.GetIssuerInfoByIndex(mIndex);
      if (!pIssuer) {
        continue;
      }

      QJsonArray result;
      QString str("data|issuer|");
      str.append(QString::number(mIndex));
      result.push_back(str);
      str =
          QString::fromLocal8Bit(pIssuer->field((int)kIssuerInfo::Full_Name_C));
      if (!str.isEmpty()) {
        result.push_back(str);
        results.push_back(result);
      }

      ++addedCount;
    }
  }
}

bool MainSearchEngine::isMatch(const CBondInfo& info, const char* text, int len,
                               bool bIsAllNumber) {
  if ((m_flag & BSF_EXCLUDE_ADDISSUEBOND) && info.IsAddIssueBond())
    return false;
  if ((m_flag & BSF_RESTRIC_ABS) &&
      !(info.IsBondABS() || strcmp("FRN", info.GetCouponType()) == 0))
    return false;
  if ((m_flag & BSF_SELECT_ABSBOND_CIB) &&
      (!info.IsBondABS() || info.IsExchange()))
    return false;
  if ((m_flag & BSF_RESTRIC_FIX_RATE) &&
      strcmp("FRN", info.GetCouponType()) == 0)
    return false;
  if ((m_flag & BSF_RESTRIC_INTEREST) && !info.IsRatingDebt()) return false;
  if (bIsAllNumber) {
    if (IsStartWith(info.GetCombBondCode(), text, len)) {
      return true;
    }
  } else {
    if (IsStartWith(info.GetCombBondCode(), text, len) ||
        GbkStrStr(info.GetBondShortName(), text) != 0 ||
        info.MatchJianpin(text) || info.MatchQuanpin(text)) {
      return true;
    }

    if (m_flag & BSF_ENGLISHFLAG) {
      static char sEnName[128];
      memset(sEnName, 0, sizeof(sEnName));
      FIELDCOPY(sEnName, info.GetBondShortNameEn().c_str());
      string_ToUpper(sEnName, 128);
      if (strstr(sEnName, text) != 0) {
        return true;
      }
    }
  }

  return false;
}

bool MainSearchEngine::IsStringNumber(const char* key) {
  bool bIsStringNumber = true;
  if (!key) return false;
  for (int i = 0; i < 1000; ++i) {
    if (!key || *key == 0) return bIsStringNumber;
    if (*key < '0' || *key > '9') {
      bIsStringNumber = false;
      break;
    }
    key++;
  }
  return bIsStringNumber;
}

bool MainSearchEngine::OnResult(const int* ids, int ncount, const char* key,
                                int len) {
  int resCount = 0;
  CBondContainer::ScopedLock lock(CBondContainer::instance());
  int bond_count = CBondContainer::instance().GetSize();

  QJsonArray results;
  if (ids != nullptr && ncount > 0 /* && m_keylength>=2*/) {
    std::vector<int> bonds;
    bonds.reserve(ncount);
    for (int i = 0; i < ncount; i++) {
      int bid = qb::base::spell::getkey(ids[i]);
      bonds.push_back(bid);
    }

    const std::vector<int>& ids_results = SortResults(&bonds[0], ncount);
    // 优先判定是否均为数字
    bool bIsAllNumber = IsStringNumber(key);
    for (int i = 0; i < ncount; i++) {
      int bid = ids_results[i];
      if (bid < 0 || bid >= bond_count) continue;
      const CBondInfo& bi = CBondContainer::instance().ElementAtR(bid);
      if (bi.IsValidBond() && isMatch(bi, key, len, bIsAllNumber)) {
        QJsonArray result;
        QString str = QString::fromLocal8Bit(bi.GetCombBondKey());
        result.push_back(str);

        str = QString::fromLocal8Bit(bi.GetCombBondCode());
        if (str.isEmpty()) str = QStringLiteral("--");
        result.push_back(str);

        str = QString::fromLocal8Bit(bi.GetBondShortName());
        if (str.isEmpty()) str = QStringLiteral("--");
        result.push_back(str);

        str = QString::fromLocal8Bit(bi.GetBondRating());
        if (str.isEmpty()) str = QStringLiteral("--");
        result.push_back(str);

        str = QString::fromLocal8Bit(bi.GetRemainDate());
        if (str.isEmpty()) str = QStringLiteral("--");
        result.push_back(str);

        results.push_back(result);
        resCount++;
        if (resCount >= 100) {
          break;
        }
      }
    }
  }

  {
    std::lock_guard<std::mutex> locker(m_lock);
    QString keyStr = QString::fromLocal8Bit(key);
    if (/*resCount > 0 && */ m_searchHis[keyStr] == m_curSearchSerialNum)
      m_fun(m_curSearchSerialNum, results);
    m_searchHis.erase(keyStr);
  }

  return true;
}

void MainSearchEngine::onResultSelected(const QString& key) {
  std::string bondKey = key.toUtf8().data();
  QbBondModule::instance().app()->execCommand(CMD_BondDetail, bondKey.c_str());
}

void MainSearchEngine::doSearch(int serialNum, const QString& key) {
  qb::base::SpellSearch::FindBonds(key.toStdWString(), this);
}

void BondInterSearchEngine::setBondData(QSharedPointer<DetailData> data) {
  QB_CHECK_RETURN_VOID1(data);
  m_data = data;
}

void BondInterSearchEngine::onResultSelected(const QString& key) {
  QB_CHECK_RETURN_VOID1(m_data);
  QByteArray combKey = key.toLatin1();
  const char* str = combKey.data();
  m_data->getBondDetailDlg()->setCombondKey(str);
}